package com.audaque.springboot.foshanupload.web.testdemo.service.impl;

import com.audaque.springboot.foshanupload.web.testdemo.service.inface.DemoService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.ContextConfiguration;

/**
 * @author zgb
 * @desc ...
 * @date 2023-09-02 16:29:21
 */
@ContextConfiguration(classes = DemoServiceImpl.class) // 此处我们的IoC只扫描注入 v 类
@SpringBootTest
public class DemoServiceImplTest2 {
    @SpyBean
    private DemoService demoService;

    @Test
    @DisplayName("SpyBean的错误打桩姿势")
    void testBadSpyBean() {
        // 与 MockBean 类似，此处也不再需要 Mockito.spy(xxx.class) 创建 spy 对象实例
        // IoC 中也同样有一个 spy 对象实例，详情参见 @MockBean 的Test示例

        // 打桩错误示例：参照 Mock 打桩的写法，预期对 serviceA 方法打桩，
        // 并且当入参等于 "asdf" 时，返回特定值 "SpyBean：serviceA方法入参等于asdf时的特定返回值"
        Mockito.when(demoService.serviceA(Mockito.eq("asdf")))
                .thenReturn("SpyBean：serviceA方法入参等于asdf时的特定返回值");

        // 由于上边已经打桩了，并且这个调用正好命中打桩规则，因次返回值将是 "SpyBean：serviceA方法入参等于asdf时的特定返回值"
        String asdf = demoService.serviceA("asdf");
        System.out.println("asdf = " + asdf);
        Assertions.assertEquals("SpyBean：serviceA方法入参等于asdf时的特定返回值", asdf);
        // 注意：上述对入参等于 "asdf" 时，被"spy错误打桩"的方法 serviceA 的返回值的断言，是可以跑通的
        // 但是，存在如下两个问题：
        // 1. serviceA 被真实调用了；（这一点往往不是我们的预期）
        // 2. serviceA 被真实调用时，它的实际入参，其实并不是 "asdf"，而是 null。
        // （想象一下如果 serviceA 方法体内真正运行时，极有可能由于实际入参是 null 而抛出异常中断执行，这是一种灾难）

        // 下述调用，虽然 serviceA 被打桩，由于 "qwer" 并没有命中打桩点，所以它不会返回打桩点设定的返回值
        // 不同于 Mock 出来的对象，下述调用会真实地执行 serviceA 方法体代码块，而不是像 Mock 一样返回 null
        System.out.println("==============================================");
        String qwer = demoService.serviceA("qwer");
        System.out.println("qwer = " + qwer);
        Assertions.assertEquals("serviceA 真实返回：qwer", qwer);

        // 同理，serviceB 方法没有被打桩，但由于 spyService 是 Spy 出来的对象实例，
        // 不同于 Mock 出来的对象实例返回 null，对 serviceB 的调用会真实的执行目标方法，并按真实情况返回
        System.out.println("==============================================");
        String zxcv = demoService.serviceB("zxcv");
        System.out.println("zxcv = " + zxcv);
        Assertions.assertEquals("serviceB 真实返回：zxcv", zxcv);
    }

    @Test
    @DisplayName("SpyBean的正确打桩姿势")
        // 注意：由于和 testBadSpyBean() 方法是分别打桩的(都是在各自的方法体局部打桩)，因此不会互相影响
    void testGoodSpyBean() {
        // 当前测试方法(testGoodSpyBean)，不使用 Mockito.spy(xxx.class) 创建 spy 对象实例
        // 同时，由于和 testBadSpyBean() 在一个测试类中，会共用同一个 spy 对象实例(DemoBeanService的instance)

        // 对 Spy 对象的正确打桩姿势，
        // （巧了，用这种 doReturn|doThrow|doAnswer(xxx).when(obj).methodXXX() 的方式，也适用于 Mock 出来的对象）
        Mockito.doReturn("SpyBean：serviceA方法入参等于asdf时的特定返回值3")
                // 注意这里 when 里面是对象实例的变量名，而不是一个 methodCall
                .when(demoService)
                // 再通过 when 泛型方法返回的Spy实例引用，对目标方法打桩
                .serviceA(Mockito.eq("asdf"));

        // 由于上边已经打桩了，并且这个调用正好命中打桩规则，因次返回值将是 "SpyBean：serviceA方法入参等于asdf时的特定返回值3"
        String asdf = demoService.serviceA("asdf");
        System.out.println("asdf = " + asdf);
        Assertions.assertEquals("SpyBean：serviceA方法入参等于asdf时的特定返回值3", asdf);
        // 说明：由于上面正确的打桩姿势，上述被命中打桩规则的调用，不会触发目标方法的真实调用，不存在上述错误用法的2个问题。

        // 对于调用打桩方法，但没有命中打桩入参条件的调用，表现行为与上述 testBadSpy 单测代码一样
        System.out.println("==============================================");
        String qwer = demoService.serviceA("qwer");
        System.out.println("qwer = " + qwer);
        Assertions.assertEquals("serviceA 真实返回：qwer", qwer);

        // 同理，对于没有打桩的 spy 对象实例的其他方法，表现行为与上述 testBadSpy 单测代码一样
        System.out.println("==============================================");
        String zxcv = demoService.serviceB("zxcv");
        System.out.println("zxcv = " + zxcv);
        Assertions.assertEquals("serviceB 真实返回：zxcv", zxcv);
    }







}
